﻿// Copyright (c) 2012-2013 fancidev
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;

namespace Util.Forms
{
    /// <summary>
    /// Represents an item in a hierarchical object model.
    /// </summary>
    public interface IVirtualItem
    {
        /// <summary>
        /// Gets a name that uniquely identifies this item within its
        /// containing folder.
        /// </summary>
        string Name { get; }

#if false
        /// <summary>
        /// Gets a name that uniquely identifies this item globally, such
        /// that two items of the same FullName are considered identical.
        /// </summary>
        string FullName { get; }
#endif

        /// <summary>
        /// Gets the display name of the item.
        /// </summary>
        string DisplayName { get; }

        /// <summary>
        /// Gets the size of the resource item in bytes.
        /// </summary>
        //int Size { get; }
    }

    /// <summary>
    /// Provides methods to read or write the contents of an item.
    /// </summary>
    public interface IVirtualFile
    {
        /// <summary>
        /// Opens the item as a stream.
        /// </summary>
        /// <returns>A stream to access the virtual file.</returns>
        Stream Open(FileMode mode, FileAccess access, FileShare share);
    }

    public static class VirtualFileExtensions
    {
        public static Stream Open(this IVirtualFile file)
        {
            return file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
        }
    }

    /// <summary>
    /// Provides methods to enumerate the items contained in an object. 
    /// Despite its name, an object that implements this interface need not
    /// be a physical folder; it could be something like a zip archive or
    /// a collection of internet shortcuts.
    /// </summary>
    public interface IVirtualFolder // : IVirtualItem
    {
        /// <summary>
        /// Enumerates the items contained in this folder. This method must
        /// be threadsafe.
        /// </summary>
        /// <param name="type">Specifies the type of items to return.</param>
        /// <returns>An enumerable of the items contained in this folder.
        /// </returns>
        IEnumerable<IVirtualItem> EnumerateItems(VirtualItemTypes type);
    }

    [Flags]
    public enum VirtualItemTypes
    {
        None = 0,
        NonFolder = 1,
        Folder = 2,
        All = NonFolder | Folder
    }

    /// <summary>
    /// Provides methods to extract the icon associated with an item.
    /// </summary>
    public interface IExtractIcon
    {
        /// <summary>
        /// Returns a string that uniquely identifies the icon of the given
        /// type and size as would be returned by <code>ExtractIcon()</code>.
        /// </summary>
        /// <param name="type">The type of icon to extract.</param>
        /// <param name="desiredSize">The desired size of the icon. The 
        /// actual icon returned is not necessarily of this size.</param>
        /// <returns>A globally unique key that identifies the icon. The key
        /// is not case sensitive.</returns>
        /// <remarks>
        /// The returned key is used by the caller to implement a cache. If
        /// an icon of the same key already exists in the cache maintained by
        /// the caller, the caller may choose to use the cached item and save
        /// a call to <code>ExtractIcon()</code>. 
        /// 
        /// For this purpose, the returned key should be unique globally, and
        /// is not case sensitive. If null or the empty string is returned,
        /// it is treated as if the icon is unique and not cached.
        /// </remarks>
        string GetIconKey(ExtractIconType type, Size desiredSize);

        /// <summary>
        /// Extracts an icon. The returned object should be of type
        /// System.Drawing.Image or Util.Media.MultiFrameImage, or other 
        /// formats supported by the caller, or null. If the returned
        /// object implements IDisposable, the caller must dispose it
        /// when it's no longer used, whether the caller understands the
        /// object's type or not.
        /// </summary>
        object ExtractIcon(ExtractIconType type, Size desiredSize);
    }

    /// <summary>
    /// Defines the type of icon to extract.
    /// </summary>
    public enum ExtractIconType
    {
        /// <summary>
        /// The thumbnail of an item.
        /// </summary>
        Thumbnail = 0,

        /// <summary>
        /// The small icon of an item, typically 16x16 or 24x24 pixels in
        /// size.
        /// </summary>
        SmallIcon = 1,

        /// <summary>
        /// The large icon of an item, typically 32x32, 48x48, or 64x64 pixels
        /// in size.
        /// </summary>
        LargeIcon = 2,

        /// <summary>
        /// The icon to display when an item is an an active state such as
        /// being selected, checked, opened, etc. The size is typically the
        /// same as a small icon.
        /// </summary>
        StateIcon = 3,
    }

    public static class ExtractIconExtensions
    {
        public static int ExtractIconToImageList(
            this IExtractIcon vIcon, ExtractIconType type,
            System.Windows.Forms.ImageList imageList)
        {
            Size desiredSize = imageList.ImageSize;
            string key = vIcon.GetIconKey(type, desiredSize);
            int index = imageList.Images.IndexOfKey(key);
            if (index == -1) // not in cache
            {
                object icon = vIcon.ExtractIcon(type, desiredSize);
                if (icon != null)
                {
                    if (icon is Image)
                    {
                        using (icon as Image)
                        {
                            index = imageList.AddSemiTransparentImage(key, icon as Image);
#if DEBUG
                            System.Diagnostics.Debug.WriteLine(
                                   "Added icon " + index + " to image list.");
#endif
                        }
                    }
                    else if (icon is IDisposable)
                    {
                        (icon as IDisposable).Dispose();
                    }
                }
            }
            return index;
        }
    }

    /// <summary>
    /// Notifies clients that the contents of a virtual folder have changed.
    /// </summary>
    public interface INotifyFolderChanged
    {
        event EventHandler<FolderChangedEventArgs> FolderChanged;
    }

    public enum FolderChangeType
    {
        None = 0,
        ItemsAdded = 1,
        ItemIconChanged = 2,
    }

    public class FolderChangedEventArgs : EventArgs
    {
        private FolderChangeType changeType;
        private IVirtualItem item;

        public FolderChangedEventArgs(FolderChangeType changeType, IVirtualItem item)
        {
            this.changeType = changeType;
            this.item = item;
        }

        public FolderChangeType ChangeType { get { return this.changeType; } }
        public IVirtualItem Item { get { return this.item; } }
    }

    public class PhysicalFile : IVirtualItem, IVirtualFile
    {
        private FileInfo file;

        public PhysicalFile(FileInfo file)
        {
            this.file = file;
        }

        public FileInfo File { get { return file; } }

        public string Name
        {
            get { return file.Name; }
        }

        public string DisplayName
        {
            get { return file.Name; }
        }

        public Stream Open(FileMode mode, FileAccess access, FileShare share)
        {
            return new FileStream(file.FullName, mode, access, share);
        }
    }
}
